package elastic

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/olivere/elastic"
)

type limit struct {
	from int
	size int
}
type dsl struct {
	client          *elastic.Client
	index           []string //string
	typ             []string //string
	pretty          bool     //string
	must            string   //where
	must_not        string   //not where
	should          string   //or
	groupby         string   //groupby
	orderby         string   //orderby
	limit           *limit   //limit
	maxResponseSize int64
	body            string // json 查询串
	path            string //查询路径
}

// NewDSL
func NewDSL(es *Search) *dsl {
	return &dsl{
		client:          es.client,
		index:           []string{es.index},
		typ:             []string{es.typ},
		pretty:          true,
		limit:           &limit{from: 0, size: 0},
		maxResponseSize: int64(10000),
	}
}

// must
func (dsl *dsl) Where(must any) *dsl {
	switch must.(type) {
	case string:
		dsl.must = fmt.Sprintf("%s", must)
	default:
		json_str, e := json.Marshal(must)
		if e != nil {
			return nil
		}
		dsl.must = string(json_str[:])
	}
	return dsl
}

// must_not
func (dsl *dsl) NotWhere(must_not any) *dsl {
	switch must_not.(type) {
	case string:
		dsl.must_not = fmt.Sprintf("%s", must_not)
	default:
		json_str, e := json.Marshal(must_not)
		if e != nil {
			return nil
		}
		dsl.must_not = string(json_str[:])
	}
	return dsl
}

// should
func (dsl *dsl) OrWhere(should any) *dsl {
	switch should.(type) {
	case string:
		dsl.should = fmt.Sprintf("%s", should)
	default:
		json_str, e := json.Marshal(should)
		if e != nil {
			return nil
		}
		dsl.should = string(json_str[:])
	}
	return dsl
}

// Sort adds a sort order.
func (dsl *dsl) OrderBy(orderby any) *dsl {
	switch orderby.(type) {
	case string:
		dsl.orderby = fmt.Sprintf("%s", orderby)
	default:
		json_str, e := json.Marshal(orderby)
		if e != nil {
			return nil
		}
		dsl.orderby = string(json_str[:])
	}
	return dsl
}

// aggs
func (dsl *dsl) GroupBy(groupby any) *dsl {
	switch groupby.(type) {
	case string:
		dsl.groupby = fmt.Sprintf("%s", groupby)
	default:
		json_str, e := json.Marshal(groupby)
		if e != nil {
			return nil
		}
		dsl.groupby = string(json_str[:])
	}
	return dsl
}

// Page
func (dsl *dsl) Page(from, size int) *dsl {
	dsl.limit = &limit{
		from: from,
		size: size,
	}
	return dsl
}

// Index sets the names of the indices to use for search.
func (dsl *dsl) Index(index ...string) *dsl {
	dsl.index = index
	return dsl
}

// Types adds search restrictions for a list of types.
func (dsl *dsl) Type(typ ...string) *dsl {
	dsl.typ = typ
	return dsl
}

// Pretty enables the caller to indent the JSON output.
func (dsl *dsl) Pretty(pretty bool) *dsl {
	dsl.pretty = pretty
	return dsl
}

// MaxResponseSize sets an upper limit on the response body size that we accept,
// to guard against OOM situations.
func (dsl *dsl) MaxResponseSize(maxResponseSize int64) *dsl {
	dsl.maxResponseSize = maxResponseSize
	return dsl
}

// 构建 path+body
func (dsl *dsl) buildURL() error {
	var path string

	if len(dsl.index) > 0 && len(dsl.typ) > 0 {
		path = fmt.Sprintf("/%s/%s/_search", strings.Join(dsl.index, ","), strings.Join(dsl.typ, ","))
	} else if len(dsl.index) > 0 {
		path = fmt.Sprintf("/%s/_search", strings.Join(dsl.index, ","))
	} else if len(dsl.typ) > 0 {
		path = fmt.Sprintf("/_all/%s/_search", strings.Join(dsl.typ, ","))
	} else {
		path = "/_search"
	}

	path += "?pretty=" + fmt.Sprintf("%v", dsl.pretty)
	dsl.path = path

	if "" == dsl.body {
		source := []string{}

		where := []string{}
		if "" != dsl.must {
			where = append(where, `"must": `+dsl.must)
		}
		if "" != dsl.must_not {
			where = append(where, `"must_not": `+dsl.must_not)
		}
		if "" != dsl.should {
			where = append(where, `"should": `+dsl.should)
		}
		if len(where) > 0 {
			source = append(source, `"query": {"bool":{`+strings.Join(where, ",")+`}}`)
		}

		if "" != dsl.orderby {
			source = append(source, `"sort": `+dsl.orderby)
		}
		if "" != dsl.groupby {
			source = append(source, `"aggs": `+dsl.groupby)
		}

		source = append(source, `"from": `+fmt.Sprintf("%v", dsl.limit.from))
		source = append(source, `"size": `+fmt.Sprintf("%v", dsl.limit.size))

		body := `{` + strings.Join(source, ",") + `}`

		dsl.body = body
	}

	return nil
}

func (dsl *dsl) Do(ctx context.Context) (*elastic.SearchResult, error) {
	e := dsl.buildURL()
	if e != nil {
		return nil, e
	}

	res, e := dsl.client.PerformRequest(ctx, elastic.PerformRequestOptions{
		Method:          "POST",
		Path:            dsl.path,
		Body:            dsl.body,
		MaxResponseSize: dsl.maxResponseSize,
	})
	if e != nil {
		return nil, e
	}

	// Return search results
	ret := &elastic.SearchResult{}
	if err := (&elastic.DefaultDecoder{}).Decode(res.Body, ret); err != nil {
		return nil, err
	}

	return ret, nil
}

//直接用 json 查
func (dsl *dsl) Source(body string) *dsl {
	dsl.body = body
	return dsl
}

// 打印解析出来的 path及body
func (dsl *dsl) Debug() (string, string, error) {
	e := dsl.buildURL()
	if e != nil {
		return "", "", e
	}
	return dsl.path, dsl.body, nil
}
